#include <stdio.h>
#include <stdlib.h>

#include "hle.h"

#include "memmap.h"
#include "nidlist.h"

#include "hle/ExceptionManagerForKernel.h"
#include "hle/InterruptManager.h"
#include "hle/IoFileMgrForUser.h"
#include "hle/KDebugForKernel.h"
#include "hle/Kernel_Library.h"
#include "hle/LoadCoreForKernel.h"
#include "hle/LoadExecForUser.h"
#include "hle/ModuleMgrForUser.h"
#include "hle/StdioForUser.h"
#include "hle/SysMemUserForUser.h"
#include "hle/ThreadManForUser.h"
#include "hle/UtilsForUser.h"
#include "hle/sceAtrac3plus.h"
#include "hle/sceAudio.h"
#include "hle/sceCtrl.h"
#include "hle/sceDisplay.h"
#include "hle/sceGe_user.h"
#include "hle/sceImpose.h"
#include "hle/sceLibFont_HV.h"
#include "hle/sceMp3.h"
#include "hle/sceMpeg.h"
#include "hle/sceNetAdhocctl.h"
#include "hle/sceNetAdhoc.h"
#include "hle/sceNetAdhocMatching.h"
#include "hle/sceNet.h"
#include "hle/scePower.h"
#include "hle/sceRtc.h"
#include "hle/sceSasCore.h"
#include "hle/sceSuspendForUser.h"
#include "hle/sceUmdUser.h"
#include "hle/sceUsb.h"
#include "hle/sceUtility.h"

#include "hle/__InterruptManager.h"
#include "hle/__ThreadManForUser.h"

#include "log.h"

#define LIBRARY(name, lib) {name, lib, sizeof(lib) / sizeof(*lib)}

HLELibrary defaultLibs[] =
{
    LIBRARY("ExceptionManagerForKernel", ExceptionManagerForKernel),
    LIBRARY("InterruptManager",          InterruptManager),
    LIBRARY("IoFileMgrForUser",          IoFileMgrForUser),
    LIBRARY("KDebugForKernel",           KDebugForKernel),
    LIBRARY("Kernel_Library",            Kernel_Library),
    LIBRARY("LoadCoreForKernel",         LoadCoreForKernel),
    LIBRARY("LoadExecForUser",           LoadExecForUser),
    LIBRARY("ModuleMgrForUser",          ModuleMgrForUser),
    LIBRARY("sceAtrac3plus",             sceAtrac3plus),
    LIBRARY("sceATRAC3plus_Library",     sceAtrac3plus),
    LIBRARY("sceAudio",                  sceAudio),
    LIBRARY("sceCtrl",                   sceCtrl),
    LIBRARY("sceDisplay",                sceDisplay),
    LIBRARY("sceGe_user",                sceGe_user),
    LIBRARY("sceImpose",                 sceImpose),
    LIBRARY("sceLibFont",                sceLibFont_HV),
    LIBRARY("sceLibFont_HV",             sceLibFont_HV),
    LIBRARY("sceMp3",                    sceMp3),
    LIBRARY("sceMpeg",                   sceMpeg),
    LIBRARY("sceNetAdhocctl",            sceNetAdhocctl),
    LIBRARY("sceNetAdhoc",               sceNetAdhoc),
    LIBRARY("sceNetAdhocMatching",       sceNetAdhocMatching),
    LIBRARY("sceNet",                    sceNet),
    LIBRARY("scePower",                  scePower),
    LIBRARY("sceRtc",                    sceRtc),
    LIBRARY("sceSasCore",                sceSasCore),
    LIBRARY("sceSuspendForUser",         sceSuspendForUser),
    LIBRARY("sceUmdUser",                sceUmdUser),
    LIBRARY("sceUsb",                    sceUsb),
    LIBRARY("sceUtility",                sceUtility),
    LIBRARY("StdioForUser",              StdioForUser),
    LIBRARY("SysMemUserForUser",         SysMemUserForUser),
    LIBRARY("ThreadManForUser",          ThreadManForUser),
    LIBRARY("UtilsForUser",              UtilsForUser)
};
u32 numDefaultLibs = sizeof(defaultLibs) / sizeof(HLELibrary);

typedef struct
{
    u32 nid;
    HLEFunc func;
    char *name;
    char *library;
    u8 blacklisted;
    u32 address;
} HLESyscall;

HLESyscall *syscalls = NULL;
u16 numSyscalls = 0;

char *hle_getNidName(u32 nid)
{
    u32 i, j;
    for (i = 0; i < nidListNum; i++)
        for (j = 0; j < nidList[i].numFuncs; j++)
            if (nidList[i].funcs[j].nid == nid)
                return nidList[i].funcs[j].name;
    return NULL;
}

HLESyscall *hle_getSyscall(u16 id)
{
    if (id >= numSyscalls)
        return NULL;
    else
        return &syscalls[id];
}

u16 hle_getNewSyscall(const char *library, u32 nid)
{
    u32 i;
    HLEFunc func = NULL;
    HLESyscall *sysc;
    u8 foundLib = 0;
    for (i = 0; i < numDefaultLibs; i++)
    {
        if (strcmp(defaultLibs[i].name, library) == 0)
        {
            u32 j;
            foundLib = 1;
            for (j = 0; j < defaultLibs[i].numFuncs; j++)
            {
                if (defaultLibs[i].funcs[j].nid == nid) {
                    func = defaultLibs[i].funcs[j].func;
                    break;
                }
            }
            break;
        }
    }
    if (nid == NID_THRET)
        func = __KernelReturnFromThread;
    else if (nid == NID_CBRET)
        func = __KernelReturnFromCallback;
    else if (nid == NID_INTRRET)
        func = __KernelReturnFromSubIntr;
    numSyscalls++;
    if (numSyscalls == 1)
        syscalls = malloc(sizeof(HLESyscall));
    else
        syscalls = realloc(syscalls, sizeof(HLESyscall) * numSyscalls);
    sysc = &syscalls[numSyscalls - 1];
    sysc->func = func;
    sysc->nid = nid;
    sysc->name = hle_getNidName(nid);
    sysc->blacklisted = foundLib ? 1 : 0;
    sysc->address = 0;
    sysc->library = malloc(sizeof(char) * (strlen(library) + 1));
    strcpy(sysc->library, library);
    if (foundLib && func == NULL)
    {
        if (sysc->name)
            _log(WRN, HLE, "Function %s in library %s not implemented", sysc->name, library);
        else
            _log(WRN, HLE, "Function 0x%08x in library %s not implemented", sysc->nid, library);
    }
    else if (func == NULL)
        _log(WRN, HLE, "Library %s not found", library);
    _log(FUL, HLE, "Syscall %d => %s [0x%08x], %s", numSyscalls - 1, sysc->name, sysc->nid, sysc->library);
    return numSyscalls - 1;
}

char *hle_getSyscallName(u16 id)
{
    return hle_getSyscall(id)->name;
}

void hle_callSyscall(u32 op)
{
    u16 id = (op >> 6);
    HLESyscall *sysc = hle_getSyscall(id);
    if (!sysc) {
        _log(ERR, HLE, "Invalid syscall %d called!", id);
        RETURN(-1);
    }
    else if (sysc->address) {
        _log(INF, HLE, "Called function %s exported by another module", sysc->name);
        cpu.pc = sysc->address;
    }
    else if (sysc->nid == 0) {
        _log(ERR, HLE, "Unknown unresolved syscall - returning 0");
        RETURN(0);
    }
    else if (!sysc->func)
    {
        if (sysc->name)
            _log(ERR, HLE, "Not implemented syscall %s (%08x, %08x, %08x, %08x)", sysc->name, PARAM(0), PARAM(1), PARAM(2), PARAM(3));
        else
            _log(ERR, HLE, "Not implemented syscall 0x%08x (%08x, %08x, %08x, %08x)", sysc->nid, PARAM(0), PARAM(1), PARAM(2), PARAM(3));
        RETURN(-1);
    }
    else
        sysc->func();
}

void hle_addExport(const char *library, u32 nid, u32 addr)
{
    u32 i;
    for (i = 0; i < numSyscalls; i++)
    {
        if (strcmp(library, syscalls[i].library) == 0
         && syscalls[i].nid == nid
         && !syscalls[i].blacklisted) {
            syscalls[i].address = addr;
            _log(ERR, HLE, "Resolved: NID %08x => %08x", nid, addr);
        }
    }
}

